import copy
import itertools
import random

import numpy as np

import Super
import Target
import archiving
import load
import config
import update

particals = config.particals  # 种群数量
MaxIter = config.MaxIter  # 最大迭代次数
beta = config.beta  # 成年狮所占比列
Nc = config.Nc  # 成年狮母狮数量
Np = config.Np  # 幼师数量
dim = config.dim  # 维度，优化变量的个数

'''狮群算法'''
'''加入单双周和起始周'''


# 随机数种子
# random.seed(1234)

def MOLSO(tasks, task_room, task_time, classrooms, classroom_unavailable_time, course_students, distribution_required,
          distribution_penalty):
    # 优化变量的上下界
    lb = np.ones([dim, 1])  # 下边界，课程数、时间数、教室数应该都是整数，所以下边界不存在负数
    ub = len(tasks) * np.ones([dim, 1])  # 上边界,课程的数量

    # 初始化种群
    schemesList, classroomsList, conflictsList = load.Load(tasks, task_room, task_time, classrooms,
                                                           classroom_unavailable_time,
                                                           particals,distribution_required)

    # 复制种群
    copy_schemesList = copy.deepcopy(schemesList)

    # 计算适应度
    '''适应度计算  SPF  2024.9.6'''
    fitness = Target.penalty_fun(schemesList, conflictsList, course_students, classroomsList, distribution_penalty)
    #
    # 多目标问题pareto解！！！！！！！！！！！
    newParetofrontiers = Super.super(fitness)

    # 初始化个体最优，初始是个体最优就是狮子本身
    lion_p, RTlion_p, fitness_p = init_pbest(copy_schemesList, classroomsList, newParetofrontiers)

    # 快速非支配排序和拥挤距离排序，确定全局最优,即狮王的位置
    lion_g, lion_fitness, RTlion_g = archiving.init_archive(copy_schemesList, newParetofrontiers, classroomsList)

    # 进行迭代,位置更新，然后计算适应度，更新全局最优
    for t in range(MaxIter):
        update_RTmatrix = []  # 存储此次迭代过程中修改位置后的矩阵列表
        update_schemeslist = []  # 存储迭代过程中修改课程信息后方案的列表
        step_ = config.step_factory * (np.mean(ub) - np.mean(lb))  # 步长
        # 母狮扰动因子
        alphaf = int(step_ * np.exp(- 30 * t / MaxIter) ** 10)  # 决定变换课程的数量，母狮扰动因子
        # 幼狮扰动因子
        alphac = int(step_ * (MaxIter - t) / MaxIter)
        # 母狮位置更新（这里取的母狮是前Nc个狮子）
        for i in range(Nc):
            matrix_a = copy.deepcopy(RTlion_p[i])  # 此处应该为第t-1次的个体a的历史最佳位置
            update_scheme = copy.deepcopy(lion_p[i])
            while True:
                index = np.random.randint(0, Nc)  # 随机挑选一只母狮
                if index == i:
                    continue
                # 找不是自己的母狮
                p_scheme = copy.deepcopy(lion_p[index])  # 获取随机母狮的历史最优解
                for k in range(alphaf):
                    index_num = np.random.randint(0, len(tasks))  # 随机产生一个变化的课程index号,上限应为课程数
                    task_num = tasks[index_num][2]  # 教学任务号
                    # 在 当前狮子排课方案RT表中找到task_num号课程的位置，位置存储在positions_a，找开始周的教室时间片
                    positions_a = find_positions(update_scheme, task_num)
                    # 在矩阵b中找到course_num号课程的行号，列号,如果课程有多次课，那么位置应该有多个，位置列表positions
                    positions_b = find_positions(p_scheme, task_num)
                    if (positions_b == positions_a):
                        continue
                    #  判断冲突
                    conflict = False
                    if positions_b[0][0] != 0:
                        conflict = load.Conflit(positions_b[0][0], positions_b[1], matrix_a)
                    # 如果没有冲突
                    if conflict:
                        # 有冲突进行下一次
                        continue
                    # 如果没有冲突的话就把教室时间片集合添加到教学任务后面
                    # 先移除教学任务中之前的教室时间片
                    update_scheme = update.updated_scheme(update_scheme, positions_b, task_num)
                    # 移除原来的教室时间片,换成新的教师时间列表
                    if positions_b[0][0] != 0:
                        matrix_a = update.updated_matrixa(matrix_a, positions_a, positions_b, task_num)

                    for conflicts in conflictsList[i][:]:
                        if conflicts[2] == task_num:
                            conflictsList[i].remove(conflicts)

                break
            update_RTmatrix.append(matrix_a)
            update_schemeslist.append(update_scheme)

        # 幼狮位置更新
        for y in range(Nc, particals):
            # 生成0-1之间的随机数决定幼狮的移动方向
            q = np.random.random()
            matrix_c = copy.deepcopy(RTlion_p[y])
            update_scheme = copy.deepcopy(lion_p[y])
            if q < 1 / 3:
                for k in range(alphac):
                    index_num = np.random.randint(0, len(tasks))  # 随机产生一个变化的课程号,上限应为课程数
                    task_num = tasks[index_num][2]
                    # 在矩阵a中找到task_num号课程的位置，位置存储在positions_a
                    positions_c = find_positions(update_scheme, task_num)
                    # 在狮王矩阵中找到course_num号课程的行号，列号,如果课程有多次课，那么位置应该有多个，位置列表positions
                    positions_g = find_positions(lion_g, task_num)
                    if positions_c == positions_g:
                        continue
                    # 判断矩阵a中b_row和b_col的位置是否为空,如果有多个位置，可能有的位置为空，有的位置不为空，循环遍历
                    # 逼近位置
                    conflict = False
                    if positions_c[0][0] != 0:
                        conflict = load.Conflit(positions_g[0][0], positions_g[1], matrix_c)
                    if conflict:
                        continue
                    # 对于改变课程信息的课程在原有方案中删除，将update_task添加到排课方案列表中
                    # 当对一个课程号课程进行改变后，修改update_scheme表i号排课方案中task_num课程的上课信息
                    update_scheme = update.updated_scheme(update_scheme, positions_g, task_num)
                    # 如果矩阵a中b_row和b_col的位置为空，则将矩阵a中原有task_num号课程删除，
                    # 将和b_row和b_col位置变为task_num号课程，否则重新产生一个新的task_num
                    # 删除一个矩阵a中的课程
                    if positions_c[0][0] != 0:
                        matrix_c = update.updated_matrixa(matrix_c, positions_c, positions_g, task_num)

                    for conflicts in conflictsList[i][:]:
                        if conflicts[2] == task_num:
                            conflictsList[i].remove(conflicts)

            elif q >= 1 / 3 and q < 2 / 3:
                index = np.random.randint(0, Nc)  # 随机挑选一只母狮,即一个排课方案的序号，找对应序号的排课方案中，适应度值最好的个体
                # 找到狮子个体i对index狮子个体中的与i狮子个体的课程相等的课程号，将i的课程号和index的课程号进行互换，以达到位置更新的作用
                lion_b_pbest = lion_p[index]  # 获取随机母狮的历史最优解
                for k in range(alphac):
                    index_num = np.random.randint(0, len(tasks))  # 随机产生一个变化的课程号,上限应为课程数
                    task_num = tasks[index_num][2]
                    # 在矩阵a中找到task_num号课程的位置，位置存储在positions_a
                    positions_c = find_positions(update_scheme, task_num)
                    # 在矩阵b中找到task_num号课程的行号，列号,如果课程有多次课，那么位置应该有多个，位置列表positions
                    positions_b = find_positions(lion_b_pbest, task_num)
                    if positions_c == positions_b:
                        continue
                    # 判断矩阵a中b_row和b_col的位置是否为空,如果有多个位置，可能有的位置为空，有的位置不为空，循环遍历
                    # 逼近位置
                    conflict = False
                    if positions_b[0][0] != 0:
                        conflict = load.Conflit(positions_b[0][0], positions_b[1], matrix_c)
                    if conflict:
                        continue
                    # 当对一个课程号课程进行改变后，修改schemesList表i号排课方案中course_num课程的上课信息
                    update_scheme = update.updated_scheme(update_scheme, positions_b, task_num)
                    # 如果矩阵a中b_row和b_col的位置为空，则将矩阵a中原有task_num号课程删除，
                    # 将和b_row和b_col位置变为task_num号课程，否则重新产生一个新的task_num
                    # 删除一个矩阵a中的课程
                    if positions_b[0][0] != 0:
                        matrix_c = update.updated_matrixa(matrix_c, positions_c, positions_b, task_num)

                    for conflicts in conflictsList[i][:]:
                        if conflicts[2] == task_num:
                            conflictsList[i].remove(conflicts)

            else:  # 远离狮王位置活动
                for k in range(alphac):
                    index_num = np.random.randint(0, len(tasks))  # 随机产生一个变化的课程号,上限应为课程数
                    task_num = tasks[index_num][2]
                    # 在矩阵a中找到task_num号课程的位置，位置存储在positions_a
                    positions_c = find_positions(update_scheme, task_num)
                    # 在狮王矩阵中找到task_num号课程的行号，列号,如果课程有多次课，那么位置应该有多个，位置列表positions
                    positions_g = find_positions(lion_g, task_num)
                    if positions_c == positions_g:
                        continue
                    # 确定应该放task_num的位置,幼狮位置+（幼狮位置减狮王位置）,如果positions中出现负值，则产生一个新的task_num值
                    # 判断position中的值是否合法
                    new_positions_c = calculate_position(task_num, positions_c, positions_g, task_room, task_time,
                                                         classroom_unavailable_time)
                    conflict = False
                    if positions_c[0][0] != 0:
                        conflict = load.Conflit(new_positions_c[0][0], new_positions_c[1], matrix_c)
                    if conflict:
                        continue
                    # 当对一个课程号课程进行改变后，修改schemesList表i号排课方案中course_num课程的上课信息
                    # 对于改变课程信息的课程在原有方案中删除，将update_courselist添加到排课方案列表中
                    update_scheme = update.updated_scheme(update_scheme, new_positions_c, task_num)

                    # 如果矩阵a中b_row和b_col的位置为空，则将矩阵a中原有course_num号课程删除，
                    # 将和b_row和b_col位置变为course_num号课程，否则重新产生一个新的course_num
                    # 删除一个矩阵a中的课程
                    if positions_c[0][0] != 0:
                        matrix_c = update.updated_matrixa(matrix_c, positions_c, new_positions_c, task_num)

                    for conflicts in conflictsList[i][:]:
                        if conflicts[2] == task_num:
                            conflictsList[i].remove(conflicts)

            # 完成个体i的位置更新后，需同步到学生和老师的信息更新
            update_RTmatrix.append(matrix_c)
            update_schemeslist.append(update_scheme)

        copy_schemesList = copy.deepcopy(update_schemeslist)
        # 重新计算适应度值
        fitness___ = Target.penalty_fun(copy_schemesList, conflictsList, course_students, classrooms, distribution_penalty)
        # 快速非支配排序、拥挤度计算
        newParetofrontiers = Super.super(fitness___)
        # 更新个体最优
        # lion_p, RTlion_p, fitness_p = update.update_pbest(copy_schemesList, update_RTmatrix, fitness___,
        #                                                   lion_p, RTlion_p, fitness_p)
        # 更新全局最优(狮王位置)
        # lion_g, lion_fitness, RTlion_g = archiving.select_global_best(copy_schemesList, lion_g, lion_fitness,
        #                                                               newParetofrontiers, update_RTmatrix, RTlion_g)
        total_penalty = lion_fitness[1] + lion_fitness[2] + 10 * lion_fitness[3]
        print("迭代次数：", t + 1)
        print("教师惩罚值：", lion_fitness[1], "时间惩罚值：", lion_fitness[2], "学生和约束惩罚值：", lion_fitness[3], "全局最优值：", total_penalty)
    return lion_g, lion_fitness


def calculate_position(task_num, position_a, position_b, taks_room, task_time, classroom_unavailable_time):
    times = [time for time in task_time if time[0] == task_num][0]
    if position_a[0][0] != 0:
        rooms = [room for room in taks_room if room[0] == task_num][0]
        rooms_times = list(itertools.product(rooms[1], times[1]))
        for room_time in rooms_times[:]:
            for un_room in classroom_unavailable_time:
                if room_time[0][0] == un_room[0]:
                    for day, start, length, week in un_room[1]:
                        if ((int(week, 2) & int(room_time[1][3], 2) == 0) or (
                                int(day, 2) & int(room_time[1][0], 2) == 0)):
                            continue
                        # 计算选择的时间和教室时间是否有重叠
                        exist_end = start + length
                        new_end = room_time[1][1] + room_time[1][2]
                        if (start < new_end and exist_end > room_time[1][1]):
                            # 删除教室时间片组合如果长度大于1的话
                            if room_time in rooms_times:
                                rooms_times.remove(room_time)
                            # rooms_times.remove(room_time)
                    break
            break
        if len(rooms_times) > 2:
            rooms_times.remove(position_a)
            rooms_times.remove(position_b)
            new_position_a = random.choice(rooms_times)
        else:
            new_position_a = position_a
    else:
        if (len(times) > 2):
            times.remove(position_a[1])
            times.remove(position_b[1])
            new_time = random.choice(times)
            new_position_a = ((0, 0), new_time)
        else:
            new_position_a = position_a
    return new_position_a


# 发现教学任务位置
def find_positions(update_scheme, element):
    position = [pos[5] for pos in update_scheme if pos[2] == element][0]
    return position


# 初始化个体最优值
def init_pbest(init_population, init_popuRT, fitness_):  # 第一次迭代最优个体就是本身
    return init_population, init_popuRT, fitness_
